package com.example.common.utils;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.example.common.constants.RdsConstants;
import com.example.common.domain.other.RedisData;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.function.Function;

@Slf4j
@Component
public class CacheClient {

    @Autowired
    private final StringRedisTemplate stringRedisTemplate;
    private final ScheduledExecutorService scheduledExecutorService;
    private final ExecutorService cacheRebuildExecutor;

    @Autowired
    public CacheClient(StringRedisTemplate stringRedisTemplate) {
        log.info("初始化Redis缓存客户端...");
        this.stringRedisTemplate = stringRedisTemplate;
        this.scheduledExecutorService = Executors.newScheduledThreadPool(5);
        BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100);
        this.cacheRebuildExecutor = new ThreadPoolExecutor(
                5,
                20,
                60,
                TimeUnit.SECONDS,
                workQueue,
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
    }

    @PostConstruct
    public void init() {
        try {
            log.info("测试Redis连接...");
            String testKey = "test:connection";
            stringRedisTemplate.opsForValue().set(testKey, "test", 10, TimeUnit.SECONDS);
            String value = stringRedisTemplate.opsForValue().get(testKey);
            if ("test".equals(value)) {
                log.info("Redis连接测试成功");
            } else {
                log.error("Redis连接测试失败: 值不匹配");
            }
        } catch (Exception e) {
            log.error("Redis连接测试失败: {}", e.getMessage(), e);
        }
    }

    // 基础缓存设置方法
    public void set(String key, Object value, long time, TimeUnit unit) {
        try {
            log.debug("设置缓存: key={}, value={}, time={}, unit={}", key, value, time, unit);
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
        } catch (Exception e) {
            log.error("设置缓存失败: key={}, error={}", key, e.getMessage(), e);
            throw e;
        }
    }

    // 基础缓存设置方法
    public void set(String key, Object value) {
        try {
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value));
        } catch (Exception e) {
            log.error("设置缓存失败: key={}, error={}", key, e.getMessage(), e);
            throw e;
        }
    }

    public Boolean setIfAbsent(String key, Object value, long time, TimeUnit unit) {
        return stringRedisTemplate.opsForValue().setIfAbsent(key, JSONUtil.toJsonStr(value), time, unit);
    }

    // 基础缓存设置方法
    public String get(String key) {
    return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 从 Redis 获取缓存并转换为指定类型
     */
    public <T> T get(String key, Class<T> type) {
        try {
            log.debug("获取缓存: key={}, type={}", key, type);
            String value = stringRedisTemplate.opsForValue().get(key);
            if (value == null) {
                return null;
            }
            return JSONUtil.toBean(value, type);
        } catch (Exception e) {
            log.error("获取缓存失败: key={}, error={}", key, e.getMessage(), e);
            throw e;
        }
    }


    //删除缓存
    public void delete(String key) {
        stringRedisTemplate.delete(key);
    }

    // 设置带逻辑过期的缓存
    public void setWithLogicalExpire(String key, Object value, long time, TimeUnit unit) {
        System.out.println("正在设置逻辑过期");
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

    // 设置带预热的缓存
    public <R, ID> void setWithWarmUp(String keyPrefix, ID id, R value, long time, TimeUnit unit) {
        String key = keyPrefix + id;
        // 设置正常缓存
        set(key, value, time, unit);

        // 计算预热时间点（例如过期前10%的时间）
        long warmUpTime = (long) (unit.toMillis(time) * 0.9);

        // 调度预热任务
        scheduledExecutorService.schedule(() -> {
            try {
                // 使用缓存重建策略更新缓存
                String lockKey = keyPrefix + id;
                if (tryLock(lockKey)) {
                    try {
                        // 重新加载数据并更新缓存
                        setWithLogicalExpire(key, value, time, unit);
                    } finally {
                        unlock(lockKey);
                    }
                }
            } catch (Exception e) {
                log.error("缓存预热失败: {}", key, e);
            }
        }, warmUpTime, TimeUnit.MILLISECONDS);
    }

    // 缓存穿透处理策略
    public <R, ID> R queryWithPassThrough(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback,
            long time, TimeUnit unit, long nullTime, TimeUnit nullUnit) {
        String key = keyPrefix + id;
        // 从Redis查询
        String json = stringRedisTemplate.opsForValue().get(key);
        // 检查是否存在
        if (StrUtil.isNotBlank(json)) {
            return JSONUtil.toBean(json, type);
        }
        // 处理缓存穿透 - 检查是否为空值
        if (json != null) {
            return null;
        }
        // 查询数据库
        R result = dbFallback.apply(id);
        // 处理数据库中不存在的情况
        if (result == null) {
            stringRedisTemplate.opsForValue().set(key, "", nullTime, nullUnit);
            return null;
        }
        // 存在则写入缓存
        set(key, result, time, unit);
        return result;
    }

    // 缓存穿透处理策略  返回结果List版本的
    public <R, ID> List<R> queryWithPassThroughList(
            String keyPrefix, ID id, Class<R> elementType, Function<ID, List<R>> dbFallback,
            long time, TimeUnit unit, long nullTime, TimeUnit nullUnit) {
        String key = keyPrefix + id;

        // 从Redis查询
        String json = stringRedisTemplate.opsForValue().get(key);

        // 检查是否存在
        if (StrUtil.isNotBlank(json)) {
            // 将JSON字符串转换为指定类型的列表
            return JSONUtil.toList(json, elementType);
        }

        // 处理缓存穿透 - 检查是否为空值
        if (json != null) {
            return null;
        }

        // 查询数据库
        List<R> result = dbFallback.apply(id);

        // 处理数据库中不存在的情况
        if (result == null || result.isEmpty()) {
            // 在Redis中设置一个短暂的空值，防止缓存穿透
            stringRedisTemplate.opsForValue().set(key, "", nullTime, nullUnit);
            return null;
        }

        // 存在则写入缓存
        set(key, result, time, unit);
        return result;
    }

    /**
     * 缓存穿透处理策略 - 支持批量 ID 查询 List 数据
     *
     * @param keyPrefix  缓存 key 前缀，如 "user:courses:"
     * @param idList     要查询的 ID 列表
     * @param dbFallback 数据库查询函数，接收一个 ID 返回对应的 List<R>
     * @param time       正常缓存时间
     * @param unit       时间单位
     * @param nullTime   空值缓存时间
     * @param nullUnit   空值时间单位
     * @return 所有 ID 对应的数据列表（可能为空或部分空）
     */
    public <R, ID> List<R> queryBatchEntityWithPassThrough(
            String keyPrefix,
            List<ID> idList,
            Class<R> entityType,
            Function<List<ID>, List<R>> dbFallback,
            Function<R, ID> idExtractor,  // 自定义 ID 提取器
            long time, TimeUnit unit,
            long nullTime, TimeUnit nullUnit) {

        List<R> result = new ArrayList<>();
        List<ID> idsToQuery = new ArrayList<>();

        Map<ID, String> idToCacheKey = new HashMap<>();
        Map<ID, R> cachedDataMap = new HashMap<>();

        // 1. 先从缓存中查找
        for (ID id : idList) {
            String cacheKey = keyPrefix + id;
            idToCacheKey.put(id, cacheKey);

            String json = stringRedisTemplate.opsForValue().get(cacheKey);
            if (StrUtil.isNotBlank(json)) {
                R entity = JSONUtil.toBean(json, entityType);
                ID entityId = idExtractor.apply(entity); // 使用自定义函数提取 ID
                cachedDataMap.put(entityId, entity);
                result.add(entity);
            } else if (json != null) {
                // 缓存了空字符串，说明数据库没有这个数据
                continue;
            } else {
                // 未命中缓存，加入待查队列
                idsToQuery.add(id);
            }
        }

        // 2. 如果有待查 ID，则调用数据库
        if (!idsToQuery.isEmpty()) {
            List<R> dbResult = dbFallback.apply(idsToQuery);

            if (dbResult != null && !dbResult.isEmpty()) {
                // 构建主键 -> 实体映射，便于后续判断
                Map<ID, R> dbResultMap = new HashMap<>();
                for (R entity : dbResult) {
                    ID entityId = idExtractor.apply(entity); // 使用自定义函数提取 ID
                    dbResultMap.put(entityId, entity);
                }

                // 写入缓存并添加到结果集
                for (ID id : idsToQuery) {
                    R entity = dbResultMap.get(id);
                    String cacheKey = idToCacheKey.get(id);

                    if (entity != null) {
                        stringRedisTemplate.opsForValue().set(cacheKey, JSONUtil.toJsonStr(entity), time, unit);
                        result.add(entity);
                    } else {
                        stringRedisTemplate.opsForValue().set(cacheKey, "", nullTime, nullUnit);
                    }
                }
            }
        }

        return result;
    }


    // 逻辑过期处理策略 - 优化版本
    public <R, ID> R queryWithLogicalExpire(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback,
            long time, TimeUnit unit, boolean forceRefresh) {
        String key = keyPrefix + id;
        String lockKey = RdsConstants.LOCK_KEY+keyPrefix + id;

        // 从Redis查询
        String json = stringRedisTemplate.opsForValue().get(key);

        // 缓存不存在处理
        if (StrUtil.isBlank(json)) {
            System.out.println("缓存不存在："+id);
            // 尝试获取锁查询数据库并初始化缓存
            if (tryLock(lockKey)) {
                try {
                    // 再次检查缓存，避免重复查询
                    json = stringRedisTemplate.opsForValue().get(key);
                    System.out.println("逻辑过期json："+json);
                    if (StrUtil.isNotBlank(json)&&isValidJsonFormat(json)) {
                        System.out.println("缓存存在1");
                        return processLogicalExpireJson(json, type, dbFallback, key, time, unit, lockKey);
                    }

                    // 查询数据库
                    R result = dbFallback.apply(id);
                    if (result != null) {
                        // 使用逻辑过期方式写入缓存
                        System.out.println("数据写入缓存");
                        setWithLogicalExpire(key, result, time, unit);
                    }
                    return result;
                } finally {
                    unlock(lockKey);
                }
            } else {
                // 获取锁失败，直接查询数据库（适合关键数据）
                return dbFallback.apply(id);
            }
        }
        System.out.println("缓存存在2");
        // 处理缓存存在的情况
        return processLogicalExpireJson(json, type, dbFallback, key, time, unit, lockKey);
    }


    // 处理逻辑过期的JSON数据
    private <R, ID> R processLogicalExpireJson(
            String json, Class<R> type, Function<ID, R> dbFallback,
            String key, long time, TimeUnit unit, String lockKey) {

        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        R result = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        LocalDateTime expireTime = redisData.getExpireTime();

        // 检查是否过期
        if (expireTime.isAfter(LocalDateTime.now())) {
            // 未过期，直接返回
            return result;
        }
        // 已过期，尝试异步刷新
        if (tryLock(lockKey)) {
            System.out.println("逻辑过期，但是获取锁成功，异步刷新：key:"+key);
            cacheRebuildExecutor.submit(() -> {
                System.out.println("数据库查询");
                try {
                    // 查询数据库
                    @SuppressWarnings("unchecked")
                    R newResult = dbFallback.apply((ID) getIdentifierFromKey(key));
                    if (newResult != null) {
                        System.out.println("查询数据库后更新缓存，key:"+key);
                        setWithLogicalExpire(key, newResult, time, unit);
                    }
                } catch (Exception e) {
                    log.error("缓存刷新失败: {}", key, e);
                } finally {
                    System.out.println("释放锁");
                    unlock(lockKey);
                }
            });
        }

        // 无论是否刷新缓存，都返回旧数据
        return result;
    }

    // 验证是否为合法的 RedisData JSON 格式
    private boolean isValidJsonFormat(String json) {
        try {
            if (StrUtil.isBlank(json) || !json.startsWith("{")) {
                return false;
            }
            // 简单验证是否包含 data 和 expireTime 字段
            JSONObject obj = JSONUtil.parseObj(json);
            return obj.containsKey("data") && obj.containsKey("expireTime");
        } catch (Exception e) {
            return false;
        }
    }

    // 从缓存键中提取标识符（简单实现，可根据实际键格式调整）
    private Object getIdentifierFromKey(String key) {
        // 假设键格式为 "prefix:id"
        int index = key.lastIndexOf(':');
        if (index != -1 && index < key.length() - 1) {
            return key.substring(index + 1);
        }
        return key;
    }

    // 互斥锁处理策略
    public <R, ID> R queryWithMutex(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback,
            long time, TimeUnit unit, int retryTimes, long retryDelay) {
        String key = keyPrefix + id;
        String lockKey = keyPrefix + id;

        // 从Redis查询
        String json = stringRedisTemplate.opsForValue().get(key);

        // 检查是否存在
        if (StrUtil.isNotBlank(json)) {
            return JSONUtil.toBean(json, type);
        }

        // 处理缓存穿透
        if (json != null) {
            return null;
        }

        // 使用互斥锁查询数据库
        for (int i = 0; i <= retryTimes; i++) {
            if (tryLock(lockKey)) {
                try {
                    // 再次检查缓存，避免重复查询
                    json = stringRedisTemplate.opsForValue().get(key);
                    if (StrUtil.isNotBlank(json)) {
                        return JSONUtil.toBean(json, type);
                    }

                    // 查询数据库
                    R result = dbFallback.apply(id);

                    // 处理不存在的情况
                    if (result == null) {
                        stringRedisTemplate.opsForValue().set(key, "", RdsConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
                        return null;
                    }

                    // 存在则写入缓存
                    set(key, result, time, unit);
                    return result;
                } finally {
                    unlock(lockKey);
                }
            }

            // 获取锁失败，重试
            if (i < retryTimes) {
                try {
                    Thread.sleep(retryDelay);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    log.warn("缓存查询重试被中断", e);
                    break;
                }
            }
        }

        // 重试次数耗尽，直接查询数据库
        return dbFallback.apply(id);
    }

    // 自定义缓存查询策略
    public <R, ID> R queryWithCustomPolicy(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback,
            CachePolicy policy) {
        // 根据自定义策略选择合适的查询方法
        if (policy.isUseLogicalExpire()) {
            return queryWithLogicalExpire(
                    keyPrefix, id, type, dbFallback,
                    policy.getExpireTime(), policy.getTimeUnit(),
                    policy.isForceRefresh()
            );
        } else if (policy.isUseMutex()) {
            return queryWithMutex(
                    keyPrefix, id, type, dbFallback,
                    policy.getExpireTime(), policy.getTimeUnit(),
                    policy.getRetryTimes(), policy.getRetryDelay()
            );
        } else {
            return queryWithPassThrough(
                    keyPrefix, id, type, dbFallback,
                    policy.getExpireTime(), policy.getTimeUnit(),
                    policy.getNullExpireTime(), policy.getNullTimeUnit()
            );
        }
    }

    // 尝试获取锁
    private boolean tryLock(String key) {
        Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(success);
    }

    // 释放锁
    private void unlock(String key) {
        stringRedisTemplate.delete(key);
    }


    // 缓存策略配置类
    public static class CachePolicy {
        private boolean useLogicalExpire = false;
        private boolean useMutex = false;
        private long expireTime = 300; // 默认5分钟
        private TimeUnit timeUnit = TimeUnit.SECONDS;
        private long nullExpireTime = 5; // 默认空值缓存5分钟
        private TimeUnit nullTimeUnit = TimeUnit.MINUTES;
        private int retryTimes = 3; // 重试次数
        private long retryDelay = 100; // 重试延迟(毫秒)
        private boolean forceRefresh = false; // 强制刷新

        // 构造方法和Builder模式
        public static CachePolicy builder() {
            return new CachePolicy();
        }

        public CachePolicy withLogicalExpire() {
            this.useLogicalExpire = true;
            this.useMutex = false;
            return this;
        }

        public CachePolicy withMutex() {
            this.useMutex = true;
            this.useLogicalExpire = false;
            return this;
        }

        public CachePolicy withExpireTime(long expireTime, TimeUnit timeUnit) {
            this.expireTime = expireTime;
            this.timeUnit = timeUnit;
            return this;
        }

        public CachePolicy withNullExpireTime(long nullExpireTime, TimeUnit nullTimeUnit) {
            this.nullExpireTime = nullExpireTime;
            this.nullTimeUnit = nullTimeUnit;
            return this;
        }

        public CachePolicy withRetryConfig(int retryTimes, long retryDelay) {
            this.retryTimes = retryTimes;
            this.retryDelay = retryDelay;
            return this;
        }

        public CachePolicy forceRefresh() {
            this.forceRefresh = true;
            return this;
        }

        // Getters
        public boolean isUseLogicalExpire() {
            return useLogicalExpire;
        }

        public boolean isUseMutex() {
            return useMutex;
        }

        public long getExpireTime() {
            return expireTime;
        }

        public TimeUnit getTimeUnit() {
            return timeUnit;
        }

        public long getNullExpireTime() {
            return nullExpireTime;
        }

        public TimeUnit getNullTimeUnit() {
            return nullTimeUnit;
        }

        public int getRetryTimes() {
            return retryTimes;
        }

        public long getRetryDelay() {
            return retryDelay;
        }

        public boolean isForceRefresh() {
            return forceRefresh;
        }
    }
}
